#include <fhe/core/NodeFactory.h>
#include <fhe/core/FileSystem.h>
#include <Poco/SharedLibrary.h>

namespace fhe
{
  namespace core
  {
    NodeFactory::NodeFactory()
    {
    }
    
    NodeFactory::~NodeFactory()
    {
    }
    
    NodeFactory& NodeFactory::instance()
    {
      static NodeFactory nf;
      return nf;
    }
    
    INodeDesc::Ptr NodeFactory::getNodeDesc( const std::string& module, const std::string& name ) const
    {
      for ( std::vector< INodeDesc::Ptr >::const_iterator nodeDesc = nodeDescs_.begin(); nodeDesc != nodeDescs_.end(); ++nodeDesc )
      {
        if ( (*nodeDesc)->module() == module && (*nodeDesc)->name() == name )
        {
          return *nodeDesc;
        }
      }
      FHE_ERROR( "unknown nodeDesc %s.%s", module.c_str(), name.c_str() );
      throw;
    }
    
    bool NodeFactory::hasNodeDesc( const std::string& module, const std::string& name ) const
    {
      for ( std::vector< INodeDesc::Ptr >::const_iterator nodeDesc = nodeDescs_.begin(); nodeDesc != nodeDescs_.end(); ++nodeDesc )
      {
        if ( (*nodeDesc)->module() == module && (*nodeDesc)->name() == name )
        {
          return true;
        }
      }
      return false;
    }
    
    void NodeFactory::addNodeDesc( const INodeDesc::Ptr& nodeDesc )
    {
      FHE_ASSERT_MSG( !hasNodeDesc( nodeDesc->module(), nodeDesc->name() ), "duplicate nodeDesc %s.%s", nodeDesc->module().c_str(), nodeDesc->name().c_str() );
      nodeDescs_.push_back( nodeDesc );
    }
    
    bool NodeFactory::hasNodeInterface( const std::string& module, const std::string& name ) const
    {
      for ( std::vector< INodeInterface::Ptr >::const_iterator nodeInterface = nodeInterfaces_.begin(); nodeInterface != nodeInterfaces_.end(); ++nodeInterface )
      {
        if ( (*nodeInterface)->module() == module && (*nodeInterface)->name() == name )
        {
          return true;
        }
      }
      return false;
    }
    
    INodeInterface::Ptr NodeFactory::getNodeInterface( const std::string& module, const std::string& name ) const
    {
      for ( std::vector< INodeInterface::Ptr >::const_iterator nodeInterface = nodeInterfaces_.begin(); nodeInterface != nodeInterfaces_.end(); ++nodeInterface )
      {
        if ( (*nodeInterface)->module() == module && (*nodeInterface)->name() == name )
        {
          return *nodeInterface;
        }
      }
      FHE_ERROR( "unknown nodeInterface %s.%s", module.c_str(), name.c_str() );
      throw;
    }
    
    void NodeFactory::addNodeInterface( const INodeInterface::Ptr& nodeInterface )
    {
      FHE_ASSERT_MSG( !hasNodeInterface( nodeInterface->module(), nodeInterface->name() ), "duplicate nodeInterface %s.%s", nodeInterface->module().c_str(), nodeInterface->name().c_str() );
      nodeInterfaces_.push_back( nodeInterface );
    }
    
    Node* NodeFactory::buildNode( const std::string& module, const std::string& name )
    {
      if ( module != "core" && 
           !hasNodeDesc( module, name ) && 
           modules_.find( module ) == modules_.end() )
      {
        std::string libName = module + ".so";
        try
        {
          new Poco::SharedLibrary( libName );
        }
        catch ( Poco::Exception e )
        {
          FHE_ERROR( "failed to load module %s: %s", module.c_str(), e.message().c_str() );
        }
      }
      if ( !hasNodeDesc( module, name ) )
      {
        FHE_ERROR( "cannot build unknown node %s.%s", module.c_str(), name.c_str() );
      }
      return getNodeDesc( module, name )->build();
    }
    
    NodeFactory::NodeDescIterator NodeFactory::nodeDescsBegin() const
    {
      return nodeDescs_.begin();
    }
    
    NodeFactory::NodeDescIterator NodeFactory::nodeDescsEnd() const
    {
      return nodeDescs_.end();
    }
    
    NodeFactory::NodeInterfaceIterator NodeFactory::nodeInterfacesBegin() const
    {
      return nodeInterfaces_.begin();
    }
    
    NodeFactory::NodeInterfaceIterator NodeFactory::nodeInterfacesEnd() const
    {
      return nodeInterfaces_.end();
    }
  }
}
